EP19. 파인 튜닝과 LoRA

LoRA는 사전학습된 LLM의 원본 가중치를 동결하고, 작은 저랭크 어댑터 행렬(B×A)만 학습하는 PEFT 기법으로, 풀 파인튜닝의 비용 폭증과 파멸적 망각 문제를 동시에 완화한다.

0. 사전 필수 용어 (선행지식)

본 EP를 이해하려면 다음 5개 용어가 친숙해야 합니다. 익숙하지 않다면 시리즈 초반(EP0~EP8 선수지식) 또는 Hugging Face Course Ch.1·3을 먼저 학습합니다.

  1. 신경망 가중치 (Weight Matrix) — Transformer 안에서 입력 토큰 임베딩을 변환하는 행렬 파라미터. 학습이란 이 행렬의 숫자를 미세 조정하는 과정. 본문 §3·§6에서 W·ΔW로 표기.
  2. 사전학습(Pre-training) vs 파인튜닝(Fine-tuning) — 사전학습은 대규모 일반 데이터로 처음부터 학습. 파인튜닝은 사전학습 완료 모델을 도메인·작업 데이터로 추가 학습. §1·§2의 출발점.
  3. 어텐션 (Attention) / KQVO — Transformer의 핵심 연산. 토큰 간 관계를 Query(Q)·Key(K)·Value(V)·Output(O) 4개 가중치 행렬로 계산. §3·§4 Step 3에서 어댑터 적용 위치로 다룸.
  4. FFN / MLP (Feed-Forward Network) — 어텐션 직후 비선형 변환 층. ReLU/GeLU 비선형 함수가 만드는 "사고의 점프"가 깊은 추론을 가능하게 함. gate·up·down 3개 행렬. §4 Step 3, §8 비교.
  5. 저랭크 행렬 (Low-Rank Matrix) / 행렬 분해 — d×d 큰 행렬을 d×r, r×d (r ≪ d) 두 작은 행렬의 곱으로 근사. LoRA의 수학적 기반. §3·§6에서 핵심 도식.

📚 참고: HuggingFace NLP Course — Transformer 구조·토크나이저·파인튜닝 기초.

1. 주제 정의

파인 튜닝(Fine-tuning) 은 사전학습된 LLM을 특정 도메인·작업 데이터에 맞춰 추가 학습시키는 과정입니다. LoRA(Low-Rank Adaptation) 는 원본 가중치를 동결하고, 작은 저랭크 어댑터 행렬만 학습하는 대표적인 PEFT(Parameter-Efficient Fine-Tuning) 기법입니다.

핵심 아이디어: 전체 가중치 W ∈ ℝ^(d×d)를 직접 수정하지 않고, 변화량 ΔW를 두 저랭크 행렬의 곱 ΔW = B(d×r) × A(r×d) 로 표현해 학습 파라미터 수를 2·d·r 로 줄인다 (r ≪ d). 학습 후 원하면 W' = W + B×A 로 병합하여 추론 오버헤드도 제거할 수 있습니다.

2. 풀려는 문제

파인튜닝이 직면하는 실무 난점은 크게 4가지입니다.

  • 문제 1 — 학습 비용 폭증: 풀 파라미터 튜닝은 GPU 시간 + 데이터 정제 + 재평가 비용까지 누적되어, 미래 추론 비용 절감 효과를 상회하는 경우가 많음. "오늘의 1억 ≠ 3년 뒤 1억"의 화폐 시간가치 문제.
  • 문제 2 — 파멸적 망각(Catastrophic Forgetting): 이미 균형 잡힌 사전학습 신경망을 추가 학습시킬 때, 기존에 잘 동작하던 일반 능력(예: 양자역학 학습 후 뉴턴역학 응답 품질 저하)이 무너질 수 있음.
  • 문제 3 — 재평가 비용: 새 데이터셋 평가뿐 아니라, 튜닝이 기존 능력에 어떤 영향을 미쳤는지까지 전 영역 재평가가 필요. 풀튜닝마다 반복.
  • 문제 4 — 튜닝의 가치 휘발: 비싸게 튜닝하는 동안 더 좋은 베이스 모델(예: 다음 세대 GPT/Claude/Gemini)이 출시되면 기존 튜닝 가치가 급락.

💡 실무 노하우: 풀튜닝은 "큰 모델·큰 데이터·전담 ML 인프라"가 모두 갖춰진 조직에서만 ROI가 나옵니다. 그 외에는 LoRA(또는 LoRA + RAG)로 시작하는 것이 자본 효율적입니다. AI 인재의 높은 몸값은 본질적으로 "한 번에 끝낼 수 있는 노하우" 가치 — 즉 시행착오 비용 절감의 가격입니다.

3. 핵심 개념·구조

LoRA의 동작은 다음 요소로 구성됩니다.

  • 원본 가중치 W (freeze): 사전학습된 어텐션 KQVO·FFN gate/up/down 등의 행렬을 그대로 두고 grad 계산 X.
  • 어댑터 행렬 A (r × d): 학습 대상. 정규분포(Gaussian)로 초기화.
  • 어댑터 행렬 B (d × r): 학습 대상. 0으로 초기화 → 학습 전 ΔW = 0 → 원본 성능 보존.
  • 랭크 r (= K): 어댑터의 두께. 표현력과 파라미터 수의 트레이드오프 조절자. 보통 4~64.
  • target_modules: 어댑터를 삽입할 가중치 위치 집합. 기본은 KQVO + FFN 전체.
  • 병합(merge): 학습 후 W' = W + B×A 흡수. 추론 그래프에서 어댑터 사라짐.
            ┌──────────────────────────────────────┐
            │  Original Attention Layer (frozen)   │
            │                                      │
            │     ┌─── W (d × d)  ──────┐          │
            │     │  100% frozen       │           │
            │     └────────────────────┘           │
            │                +                     │
            │     ┌─── B (d × r) ──┐               │
            │     │   trainable    │               │
            │     └────────┬───────┘               │
            │              ×                       │
            │     ┌─── A (r × d) ──┐               │
            │     │   trainable    │               │
            │     └────────────────┘               │
            │                                      │
            │     ΔW = B × A   (rank ≤ r)          │
            │     W_new = W + ΔW                   │
            └──────────────────────────────────────┘

4. 구현 가이드 (Do It Yourself)

시작 전 (Before you begin)

이 섹션을 완료하면 Hugging Face PEFT 라이브러리로 LoRA 어댑터를 정의·학습·병합하는 전 과정을 직접 코드로 구현하고 검증할 수 있습니다.

선수 조건: - Python 3.10+ - pip install transformers peft accelerate bitsandbytes datasets - GPU (CUDA) 1장 이상 (7B 모델 기준 VRAM ≥ 16GB) - Hugging Face Hub 토큰 환경 변수 HF_TOKEN 설정

소요 시간: 약 30분 (코드 작성 + 1 epoch 미니 학습).

Step 1 — 베이스 모델·토크나이저 로딩

목표: 사전학습 LLM과 토크나이저를 4-bit 양자화로 메모리 절감하여 로드합니다.

다음 코드를 train_lora.py 에 추가합니다.

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch

bnb = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
)
base_model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B-Instruct",
    quantization_config=bnb,
    device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B-Instruct")

이 코드는 QLoRA 패턴 (4-bit 양자화 + LoRA) 의 첫 단계입니다.

⚠️ 주의: HF_TOKEN 을 코드에 하드코딩하지 마세요. export HF_TOKEN=... 환경 변수 사용.

💡 실무 노하우: bnb_4bit_compute_dtype=torch.bfloat16 은 A100/H100에서 최적. 구형 GPU(T4)는 float16 사용.

📚 참고: bitsandbytes README · Hugging Face PEFT.

확인: base_model 의 파라미터가 4-bit dtype 으로 로드되고 GPU에 분산 배치되었는지 print(base_model.hf_device_map) 로 확인.

Step 2 — LoRA 설정 (LoraConfig)

목표: 어댑터 랭크·적용 위치·dropout 을 선언합니다.

from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training

base_model = prepare_model_for_kbit_training(base_model)

lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                    "gate_proj", "up_proj", "down_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)
model = get_peft_model(base_model, lora_config)
model.print_trainable_parameters()
  • r=16 — 어댑터 두께(rank). 8~32가 일반 sweet spot.
  • lora_alpha=32 — 스케일링 계수. 보통 2*r.
  • target_modules — Attention KQVO + FFN gate/up/down 전체. 강의에서 설명한 표준 적용 범위.

💡 실무 노하우: 자원이 빠듯하면 ["q_proj", "v_proj"] 만 남기세요 (지식 위주 튜닝 가성비). 추론 능력 확장이 목표면 FFN(gate/up/down)도 포함.

📚 참고: Hu et al. 2021, "LoRA: Low-Rank Adaptation of Large Language Models" — 원 논문.

확인: model.print_trainable_parameters() 출력에서 학습 파라미터 비율이 전체 대비 0.1~1% 수준 인지 확인.

Step 3 — 학습 루프 (SFTTrainer)

목표: TRL의 SFTTrainer 로 데이터셋을 학습합니다.

from trl import SFTTrainer, SFTConfig
from datasets import load_dataset

ds = load_dataset("tatsu-lab/alpaca", split="train[:1000]")
args = SFTConfig(
    output_dir="./lora-llama3",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    learning_rate=2e-4,
    num_train_epochs=1,
    logging_steps=10,
    save_steps=100,
)
trainer = SFTTrainer(model=model, args=args, train_dataset=ds, tokenizer=tokenizer)
trainer.train()
trainer.save_model("./lora-llama3-final")

⚠️ 주의: learning_rate=2e-4 는 LoRA 전용 권장값. 풀 파인튜닝(1e-5)보다 한 자리 큼.

확인: ./lora-llama3-final/adapter_model.safetensors 가 생성되고 베이스 모델 대비 수십~수백 MB로 작은지 확인.

Step 4 — 추론 (어댑터 로드)

목표: 어댑터를 베이스 모델에 동적으로 붙여 추론합니다.

from peft import PeftModel

base = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.1-8B-Instruct",
                                            quantization_config=bnb, device_map="auto")
model = PeftModel.from_pretrained(base, "./lora-llama3-final")
out = model.generate(**tokenizer("LoRA의 핵심 원리는?", return_tensors="pt").to("cuda"),
                     max_new_tokens=128)
print(tokenizer.decode(out[0], skip_special_tokens=True))

확인: 도메인 응답이 학습 전 베이스 모델 응답과 다르게(개선되어) 나오는지 확인.

Step 5 — 동작 확인 (병합 후 서빙)

목표: 어댑터를 베이스 모델에 흡수시켜 추론 오버헤드를 제거합니다.

merged = model.merge_and_unload()
merged.save_pretrained("./llama3-lora-merged")
tokenizer.save_pretrained("./llama3-lora-merged")

예상 출력: ./llama3-lora-merged/ 폴더에 어댑터가 흡수된 풀 가중치 모델이 저장되며, 이후엔 PeftModel 없이 AutoModelForCausalLM.from_pretrained("./llama3-lora-merged") 로 일반 모델처럼 로딩됩니다. 추론 속도는 베이스 모델과 동일.

⚠️ 주의: 병합 후엔 어댑터를 다시 분리할 수 없습니다. 후속 튜닝 가능성이 있으면 어댑터 파일 별도 보관.

5. 적용 사례 (공신력 오픈소스)

  • Hugging Face PEFT (https://github.com/huggingface/peft) — LoRA·QLoRA·AdaLoRA·IA³ 등 PEFT 기법의 사실상 표준 라이브러리. LoraConfig API 가 본 가이드의 출발점.
  • HuggingFace Transformers (https://github.com/huggingface/transformers) — 베이스 모델 로딩·토크나이저·생성 파이프라인.
  • TRL (Transformer Reinforcement Learning) (https://github.com/huggingface/trl) — SFTTrainer·DPOTrainer 로 LoRA + SFT/DPO 워크플로 지원.
  • bitsandbytes (https://github.com/bitsandbytes-foundation/bitsandbytes) — 4-bit/8-bit 양자화. QLoRA의 핵심 의존성.
  • vLLM (https://github.com/vllm-project/vllm) — 병합 후 모델 또는 LoRA 어댑터를 그대로 서빙. --enable-lora 플래그로 다중 어댑터 hot-swap 지원.
  • Ollama (https://github.com/ollama/ollama) — 병합된 LoRA 모델을 ModelfileFROM 지시문으로 로컬 배포.
  • LLaMA-Factory (https://github.com/hiyouga/LLaMA-Factory) — LoRA/QLoRA GUI/CLI 파인튜닝 프레임워크.
  • Axolotl (https://github.com/axolotl-ai-cloud/axolotl) — 설정 파일 기반 LoRA 파인튜닝 자동화.
  • Unsloth (https://github.com/unslothai/unsloth) — LoRA 학습 속도 2배·VRAM 절반 최적화.
  • LangChain (https://github.com/langchain-ai/langchain) — 병합 후 LoRA 모델을 HuggingFacePipeline 으로 체인에 결합.

📚 참고: 위 OSS는 모두 GitHub stars ≥ 5K (대부분 ≥ 20K) 이며 Apache 2.0 또는 MIT 라이선스.

💡 실무 노하우: 입문은 PEFT + TRL 조합으로 시작하고, 자동화 단계로 진입하면 Axolotl 의 YAML 설정으로 실험 재현성을 확보하는 패턴이 일반적입니다.

6. 핵심 원리

LoRA의 효율성은 단 한 줄의 행렬 수식에서 나옵니다:

W_new = W_orig + B × A     (rank(B×A) ≤ r ≪ d)
  • 원리 1 — 저랭크 가정: 사전학습 모델을 도메인 데이터에 적응시킬 때 필요한 ΔW는 본질적으로 저랭크라는 경험적 관찰. 따라서 d×d 풀랭크 갱신 대신 2dr 파라미터로 충분.
  • 원리 2 — 0 초기화의 묘: B = 0 이면 학습 전 ΔW = 0 → 어댑터 삽입만으로는 원본 출력 변화 없음. 학습이 진행되며 점진적으로 B가 0에서 벗어나 안정적으로 학습 신호가 누적.

7. 변형·확장

  • QLoRA: 베이스 모델을 4-bit 양자화 + LoRA. 24GB GPU 1장으로 65B 모델 튜닝 가능 (Dettmers et al. 2023).
  • AdaLoRA: rank를 학습 도중 동적으로 재분배. 중요한 레이어에 더 큰 r 할당.
  • DoRA (Weight-Decomposed LoRA): ΔW를 방향·크기로 분해해 풀튜닝에 더 근접한 품질.
  • IA³: A·B 행렬 대신 가중치 벡터로 스케일만 학습. LoRA 보다 더 적은 파라미터.
  • LoRA + Prompt/Prefix Tuning 결합: 어댑터 + 학습 가능한 임베딩 동시 사용.

8. 다른 도구·접근과의 비교 (3-way)

측면 LoRA 풀 파인튜닝 RAG (검색증강)
학습 파라미터 0.1~1% 100% 0% (인덱스 구축만)
GPU 비용 낮음 (단일 GPU 가능) 매우 높음 (클러스터) 거의 없음
파멸적 망각 거의 없음 (원본 freeze) 위험 큼 해당 없음
도메인 톤·스타일 강함 (학습) 강함 (학습) 약함 (검색 결과 의존)
최신 지식 주입 학습 데이터 시점 고정 학습 데이터 시점 고정 인덱스 갱신만으로 즉시
추론 속도 (병합 후) 베이스와 동일 베이스와 동일 검색 단계 오버헤드
사실성 (Hallucination) 학습 데이터 의존 학습 데이터 의존 검색 결과로 근거 제공

9. 한계·트레이드오프

  1. 저랭크 가정의 한계 — 작업이 베이스 모델과 크게 동떨어지면 r=8~32로 부족. r 증가하면 LoRA 이점 희석.
  2. r·alpha 하이퍼파라미터 민감도lora_alpha / r 비율이 학습률처럼 작용. 잘못 잡으면 underfit/overfit.
  3. target_modules 선택 의존성 — 어텐션만 vs FFN 포함 결정이 결과 품질에 직접 영향. 자동 결정 도구 부재.
  4. 병합 후 비가역성merge_and_unload 이후 어댑터 회수 불가. 다중 도메인 hot-swap이 필요하면 병합 보류.
  5. 양자화 + LoRA의 수치 손실 — QLoRA는 4-bit 양자화로 일부 정확도 손실. 정밀도 민감 작업에는 LoRA(non-quantized) 추천.

10. 최신 권장 패턴 (2026 기준)

  • QLoRA 표준화: 24GB GPU 1장으로 7B~13B 튜닝, 80GB 1장으로 70B 튜닝이 새로운 표준 (2026-05 기준).
  • LoRA Hub / 다중 어댑터: 베이스 모델 1개에 도메인별 어댑터 N개를 동적 로딩하여 멀티테넌트 서빙 (vLLM --enable-lora).
  • DPO + LoRA 결합: SFT-LoRA 이후 DPO-LoRA로 선호도 학습. TRL DPOTrainer 가 표준 (2026-05 기준).
  • r 자동 탐색: AdaLoRA·LoRA-FA 등 r 자동 결정 기법이 연구 단계에서 실무 단계로 이동 중.
  • MoE + LoRA: Mixtral 류 MoE 모델에 expert 별 LoRA 적용 (2026-05 기준, vLLM 지원 확장 중).

💡 실무 노하우: 2026년 시점 신규 프로젝트라면 QLoRA + DPO + vLLM 서빙 조합이 사실상 디폴트입니다. RAG와 결합해 "도메인 톤은 LoRA, 사실은 RAG" 분업이 권장.

11. 메타인지 자기평가

본인 프로젝트에 LoRA 도입 가능성 검증 절차:

Step 1 — 현재 상태 점검

nvidia-smi
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"
pip show peft transformers trl bitsandbytes

Step 2 — 적용 가능성 평가

  • 조건 1: 도메인 데이터 1k~100k 샘플 확보 가능 (소량이면 LoRA, 대량이면 풀튜닝 검토)
  • 조건 2: 작업이 베이스 모델 능력과 가까움 (멀면 r 키우거나 풀튜닝)
  • 조건 3: 응답 톤·스타일·도메인 용어가 목표 (RAG로는 부족)
  • 조건 4: 평가셋과 메트릭이 정의됨 (없으면 튜닝 ROI 측정 불가)

Step 3 — 점진 적용

  1. 미니 데이터셋(100건)으로 r=8, 1 epoch 학습 → 형식·톤이 학습되는지 sanity check
  2. r=16, FFN 포함, 전체 데이터로 1~3 epoch 확장
  3. eval set으로 베이스 모델 대비 +N% 개선 여부 검증
  4. 통과하면 DPO 단계 진입, 미달이면 데이터 정제 또는 r 조정 회귀
  5. 최종 모델은 merge_and_unload 후 vLLM·Ollama로 서빙

검증일: 2026-05-27 (Hugging Face PEFT main, vLLM 0.x, TRL 최신). 라이브러리 API는 출시 후 변경될 수 있으므로 실 구현 시 각 OSS 의 최신 README 재확인 권장.

난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플